package org.aplikator.client.shared.rpc.marshaller;

import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.aplikator.client.shared.data.Record;
import org.jboss.errai.marshalling.client.api.Marshaller;
import org.jboss.errai.marshalling.client.api.MarshallingSession;
import org.jboss.errai.marshalling.client.api.json.EJArray;
import org.jboss.errai.marshalling.client.api.json.EJObject;
import org.jboss.errai.marshalling.client.api.json.EJString;
import org.jboss.errai.marshalling.client.api.json.EJValue;

/**
 * Data type ?? Create DataType ENUM ?
 * @author pavels
 *
 */
public class DataMarshallingUtils {

	private static final String VALUE = "value";
	private static final String TYPE = "type";

	public enum DataType {
		
		bigdecimal {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataBigDecimal(input, (BigDecimal)val);
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataBigDecimal(ejval);
			}

			@Override
			public Class handleType() {
				return BigDecimal.class;
			}
			
		},
		string {
			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataString(input, (String)val);
			}

			
			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataString(ejval);
			}


			@Override
			public Class handleType() {
				return String.class;
			}
		}, 
		integer {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataInteger(input, (Number)val);
			}

			
			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataInteger(ejval);
			}


			@Override
			public Class handleType() {
				return Integer.class;
			}
			
		}, 
		doublev {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataDouble(input, (Number)val);
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataDouble(ejval);
			}


			@Override
			public Class handleType() {
				return Double.class;
			}

			@Override
			public String typeName() {
				return "double";
			}
			
		}, 
		bool {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataBoolean(input, (Boolean)val);
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataBoolean(ejval);
			}


			@Override
			public Class handleType() {
				return Boolean.class;
			}

			@Override
			public String typeName() {
				return "boolean";
			}
		}, 

		date {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataDate(input, (Date)val);
			}

			
			
			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataDate(ejval);
			}



			@Override
			public Class handleType() {
				return Date.class;
			}
			
		},
		record {

			@Override
			public StringBuilder marshall(StringBuilder input,
					Serializable val, MarshallingSession ctx) {
				return dataRecord(input, (Record)val, ctx);
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataRecord(ejval, ctx);
			}

			@Override
			public Class handleType() {
				return Record.class;
			}
			
		},
		records {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataRecords(input, val, ctx);
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return (Serializable) dataRecords(ejval, ctx);
			}

			@Override
			public Class handleType() {
				//TODO: Change it!!
				return ArrayList.class;
			}
			
		},
		timestamp {
			
			
			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				return dataTimestamp(input, (Timestamp)val);
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return dataTimestamp(ejval);
			}

			@Override
			public Class handleType() {
				return Timestamp.class;
			}
		},
		nullv {

			@Override
			public StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx) {
				input.append('{');
				MarshallingUtils.nullval(VALUE,  input);
				input.append('}');
				return input;
			}

			@Override
			public Serializable demarshall(EJValue ejval, MarshallingSession ctx) {
				return null;
			}

			@Override
			public Class handleType() {
				return null;
			}
			
		}
		;
		
		public abstract StringBuilder marshall(StringBuilder input, Serializable val, MarshallingSession ctx);

	
		public abstract Serializable demarshall(EJValue ejval, MarshallingSession ctx);
		
		public abstract Class handleType();
		
		public String typeName() {
			return name();
		}
		
		


		public static DataType lookup(Serializable val) {
			if (val == null) return DataType.nullv;
			DataType[] vals = values();
			for (DataType dt : vals) {
				if (dt.handleType() != null && dt.handleType().equals(val.getClass())) {
					return dt;
				}
			}
			return null;
		}
		
		public static DataType lookup(EJValue val) {
			if (val.isObject() != null) {
				EJObject obj = val.isObject();
				if (obj.containsKey(TYPE)) {
					EJString ejString = obj.get(TYPE).isString();
					if (ejString != null) {
						String type = ejString.stringValue();
						DataType[] vals = values();
						for (DataType dt : vals) {
							if (dt.typeName().equals(type)) {
								return dt;
							}
						}
					}
				} else return DataType.nullv;
			} 
			return null;
		}
	}

	public static StringBuilder dataBigDecimal(StringBuilder input, Serializable val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.bigdecimal.name(), input).append(',');
		MarshallingUtils.key(VALUE, input).append(':');
		input.append(val.toString());
		input.append('}');
		return input;
		
	}

	public static BigDecimal dataBigDecimal(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			return new BigDecimal(obj.get(VALUE).isNumber().doubleValue());
		}
		return null;
	}
	
	public static StringBuilder dataRecord(StringBuilder input, Serializable val, MarshallingSession ctx) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.record.name(), input).append(',');
		MarshallingUtils.key(VALUE, input).append(':');
		input.append(ctx.getMarshallerInstance(Record.class.getName()).marshall(val, ctx));
		input.append('}');
		return input;
	}
	
	public static Record dataRecord(EJValue val, MarshallingSession session) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			EJValue ejVal = obj.get(VALUE);
			Marshaller<Object> marsh = session.getMarshallerInstance(Record.class.getName());	
			return (Record) marsh.demarshall(ejVal, session);
		}
		return null;
	}
	
	public static StringBuilder dataRecords(StringBuilder input, Serializable val, MarshallingSession ctx) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.records.name(), input).append(',');
		MarshallingUtils.key(VALUE, input).append(':');
		MarshallingUtils.listMarshall(input, (List)val, ctx, Record.class.getName());
		input.append('}');
		return input;
	}

	public static List dataRecords(EJValue val, MarshallingSession session) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			return DemarshallingUtils.listUnMarshall(obj.get(VALUE), session, Record.class.getName());
		}
		return new ArrayList();
	}

	
	public static StringBuilder dataString(StringBuilder input, String val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.string.name(), input).append(',');
		MarshallingUtils.string(VALUE, val, input);
		input.append('}');
		return input;
	}

	public static String dataString(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			String strVal = obj.get(VALUE).isString().stringValue();
			return DemarshallingUtils.unescape(strVal);
		}
		return null;
	}

	public static StringBuilder dataInteger(StringBuilder input, Number val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.integer.typeName(), input).append(',');
		MarshallingUtils.number(VALUE, val, input);
		input.append('}');
		return input;
	}

	public static Integer dataInteger(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			return obj.get(VALUE).isNumber().intValue();
		}
		return null;
	}

	
	
	public static StringBuilder dataDouble(StringBuilder input, Number val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.doublev.typeName(), input).append(',');
		MarshallingUtils.number(VALUE, val, input);
		input.append('}');
		return input;
	}
	

	public static Double dataDouble(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			return obj.get(VALUE).isNumber().doubleValue();
		}
		return null;
	}


	public static StringBuilder dataTimestamp(StringBuilder input, Timestamp val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.timestamp.typeName(), input).append(',');
		MarshallingUtils.string(VALUE, ""+val.getTime(), input);
		input.append('}');
		return input;
	}

	public static Date dataTimestamp(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			long parseLong = Long.parseLong(obj.get(VALUE).isString().stringValue());
			return new Timestamp(parseLong);
		}
		return null;
	}

	
	public static StringBuilder dataDate(StringBuilder input, Date val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.date.typeName(), input).append(',');
		MarshallingUtils.string(VALUE, ""+val.getTime(), input);
		input.append('}');
		return input;
	}

	public static Date dataDate(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			long parseLong = Long.parseLong(obj.get(VALUE).isString().stringValue());
			return new Date(parseLong);
		}
		return null;
	}

	public static StringBuilder dataBoolean(StringBuilder input, Boolean val) {
		input.append('{');
		MarshallingUtils.string(TYPE, DataType.bool.typeName(), input).append(',');
		MarshallingUtils.bool(VALUE, val, input);
		input.append('}');
		return input;
	}

	public static Boolean dataBoolean(EJValue val) {
		if (val.isObject() != null) {
			EJObject obj = val.isObject();
			return obj.get(VALUE).isBoolean().booleanValue();
		}
		return null;
	}


	public static void main(String[] args) {
		BigDecimal decimal = new BigDecimal("33.333333333333333333333333333333");
		String str = decimal.toString();
		System.out.println(decimal.toPlainString());
		System.out.println(decimal.toString());
		
	}
}
